// Copyright (c) 2011-2016 The Bitcoin Core developers
// Copyright (c) 2017-2019 The Raven Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "key.h"
#include "keystore.h"
#include "policy/policy.h"
#include "script/script.h"
#include "script/script_error.h"
#include "script/interpreter.h"
#include "script/sign.h"
#include "script/ismine.h"
#include "uint256.h"
#include "test/test_raven.h"


#include <boost/test/unit_test.hpp>


BOOST_FIXTURE_TEST_SUITE(multisig_tests, BasicTestingSetup)

    CScript
    sign_multisig(CScript scriptPubKey, std::vector<CKey> keys, CTransaction transaction, int whichIn)
    {
        uint256 hash = SignatureHash(scriptPubKey, transaction, whichIn, SIGHASH_ALL, 0, SIGVERSION_BASE);

        CScript result;
        result << OP_0; // CHECKMULTISIG bug workaround
        for (const CKey &key : keys)
        {
            std::vector<unsigned char> vchSig;
            BOOST_CHECK(key.Sign(hash, vchSig));
            vchSig.push_back((unsigned char) SIGHASH_ALL);
            result << vchSig;
        }
        return result;
    }

    BOOST_AUTO_TEST_CASE(multisig_verify_test)
    {
        BOOST_TEST_MESSAGE("Running MultiSig Verify Test");

        unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC;

        ScriptError err;
        CKey key[4];
        CAmount amount = 0;
        for (int i = 0; i < 4; i++)
            key[i].MakeNewKey(true);

        CScript a_and_b;
        a_and_b << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2
                << OP_CHECKMULTISIG;

        CScript a_or_b;
        a_or_b << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2
               << OP_CHECKMULTISIG;

        CScript escrow;
        escrow << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey())
               << ToByteVector(key[2].GetPubKey()) << OP_3 << OP_CHECKMULTISIG;

        CMutableTransaction txFrom;  // Funding transaction
        txFrom.vout.resize(3);
        txFrom.vout[0].scriptPubKey = a_and_b;
        txFrom.vout[1].scriptPubKey = a_or_b;
        txFrom.vout[2].scriptPubKey = escrow;

        CMutableTransaction txTo[3]; // Spending transaction
        for (int i = 0; i < 3; i++)
        {
            txTo[i].vin.resize(1);
            txTo[i].vout.resize(1);
            txTo[i].vin[0].prevout.n = i;
            txTo[i].vin[0].prevout.hash = txFrom.GetHash();
            txTo[i].vout[0].nValue = 1;
        }

        std::vector<CKey> keys;
        CScript s;

        // Test a AND b:
        keys.assign(1, key[0]);
        keys.push_back(key[1]);
        s = sign_multisig(a_and_b, keys, txTo[0], 0);
        BOOST_CHECK(VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err));
        BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));

        BOOST_TEST_MESSAGE("I'm here");
        for (int i = 0; i < 4; i++)
        {
            keys.assign(1, key[i]);
            s = sign_multisig(a_and_b, keys, txTo[0], 0);
            BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err), strprintf("a&b 1: %d", i));
            BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err));

            keys.assign(1, key[1]);
            keys.push_back(key[i]);
            s = sign_multisig(a_and_b, keys, txTo[0], 0);
            BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err), strprintf("a&b 2: %d", i));
            BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err));
        }
        BOOST_TEST_MESSAGE("I'm done");

        // Test a OR b:
        for (int i = 0; i < 4; i++)
        {
            keys.assign(1, key[i]);
            s = sign_multisig(a_or_b, keys, txTo[1], 0);
            if (i == 0 || i == 1)
            {
                BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err), strprintf("a|b: %d", i));
                BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));
            } else
            {
                BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err), strprintf("a|b: %d", i));
                BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err));
            }
        }
        s.clear();
        s << OP_0 << OP_1;
        BOOST_CHECK(!VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err));
        BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_SIG_DER, ScriptErrorString(err));


        for (int i = 0; i < 4; i++)
            for (int j = 0; j < 4; j++)
            {
                keys.assign(1, key[i]);
                keys.push_back(key[j]);
                s = sign_multisig(escrow, keys, txTo[2], 0);
                if (i < j && i < 3 && j < 3)
                {
                    BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, nullptr, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount), &err), strprintf("escrow 1: %d %d", i, j));
                    BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));
                } else
                {
                    BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, nullptr, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount), &err), strprintf("escrow 2: %d %d", i, j));
                    BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err));
                }
            }
    }

    BOOST_AUTO_TEST_CASE(multisig_isstandard_test)
    {
        BOOST_TEST_MESSAGE("Running MultiSig IsStandard Test");

        CKey key[4];
        for (int i = 0; i < 4; i++)
            key[i].MakeNewKey(true);

        txnouttype whichType;

        CScript a_and_b;
        a_and_b << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2
                << OP_CHECKMULTISIG;
        BOOST_CHECK(::IsStandard(a_and_b, whichType));

        CScript a_or_b;
        a_or_b << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2
               << OP_CHECKMULTISIG;
        BOOST_CHECK(::IsStandard(a_or_b, whichType));

        CScript escrow;
        escrow << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey())
               << ToByteVector(key[2].GetPubKey()) << OP_3 << OP_CHECKMULTISIG;
        BOOST_CHECK(::IsStandard(escrow, whichType));

        CScript one_of_four;
        one_of_four << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey())
                    << ToByteVector(key[2].GetPubKey()) << ToByteVector(key[3].GetPubKey()) << OP_4 << OP_CHECKMULTISIG;
        BOOST_CHECK(!::IsStandard(one_of_four, whichType));

        CScript malformed[6];
        malformed[0] << OP_3 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2
                     << OP_CHECKMULTISIG;
        malformed[1] << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_3
                     << OP_CHECKMULTISIG;
        malformed[2] << OP_0 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2
                     << OP_CHECKMULTISIG;
        malformed[3] << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_0
                     << OP_CHECKMULTISIG;
        malformed[4] << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey())
                     << OP_CHECKMULTISIG;
        malformed[5] << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey());

        for (int i = 0; i < 6; i++)
            BOOST_CHECK(!::IsStandard(malformed[i], whichType));
    }

    BOOST_AUTO_TEST_CASE(multisig_sign_test)
    {
        BOOST_TEST_MESSAGE("Running MultiSig Sign Test");

        // Test SignSignature() (and therefore the version of Solver() that signs transactions)
        CBasicKeyStore keystore;
        CKey key[4];
        for (int i = 0; i < 4; i++)
        {
            key[i].MakeNewKey(true);
            keystore.AddKey(key[i]);
        }

        CScript a_and_b;
        a_and_b << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2
                << OP_CHECKMULTISIG;

        CScript a_or_b;
        a_or_b << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2
               << OP_CHECKMULTISIG;

        CScript escrow;
        escrow << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey())
               << ToByteVector(key[2].GetPubKey()) << OP_3 << OP_CHECKMULTISIG;

        CMutableTransaction txFrom;  // Funding transaction
        txFrom.vout.resize(3);
        txFrom.vout[0].scriptPubKey = a_and_b;
        txFrom.vout[1].scriptPubKey = a_or_b;
        txFrom.vout[2].scriptPubKey = escrow;

        CMutableTransaction txTo[3]; // Spending transaction
        for (int i = 0; i < 3; i++)
        {
            txTo[i].vin.resize(1);
            txTo[i].vout.resize(1);
            txTo[i].vin[0].prevout.n = i;
            txTo[i].vin[0].prevout.hash = txFrom.GetHash();
            txTo[i].vout[0].nValue = 1;
        }

        for (int i = 0; i < 3; i++)
        {
            BOOST_CHECK_MESSAGE(SignSignature(keystore, txFrom, txTo[i], 0, SIGHASH_ALL), strprintf("SignSignature %d", i));
        }
    }


BOOST_AUTO_TEST_SUITE_END()
